﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Threading;

namespace Swoosh
{
    
    public delegate void CombinedKeyEventHandler (List<Keys> keys);

    public class KeyHook
    {
        private delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);

        //Declare hook handle as int.
        private int hHook = 0;
        public List<Keys> KeyCodes = new List<Keys>();
        public List<Keys> KeyCodesPressed = new List<Keys>();
        
        //Declare keyboard hook constant.
        //For other hook types, you can obtain these values from Winuser.h in Microsoft SDK.
        const int WH_KEYBOARD_LL = 13;

        const int WM_KEYDOWN = 0x100;
        const int WM_KEYUP = 0x0101;
        const int WM_SYSKEYDOWN = 0x0104;
        const int WM_SYSKEYUP = 0x0105;

        HookProc KeyboardHookProcedure;

        [StructLayout(LayoutKind.Sequential)]
        private struct keyboardHookStruct
        {
            public int vkCode;
            public int scanCode;
            public int flags;
            public int time;
            public int dwExtraInfo;
        }

        //Import for SetWindowsHookEx function.
        //Use this function to install thread-specific hook.
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

        //Import for UnhookWindowsHookEx.
        //Call this function to uninstall the hook.
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        private static extern bool UnhookWindowsHookEx(int idHook);

        //Import for CallNextHookEx.
        //Use this function to pass the hook information to next hook procedure in chain.
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        private static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll")]
        private static extern IntPtr LoadLibrary(string lpFileName);

        public KeyHook()
        {
            Hook();
        }

        ~KeyHook()
        {
            UnHook();
        }

        public int Hook()
        {
            KeyboardHookProcedure = new HookProc(KeyboardHookProc);

            hHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, (IntPtr)LoadLibrary("User32"), 0);
            return hHook;
        }

        public bool UnHook()
        {
            bool ret = UnhookWindowsHookEx(hHook);
            if (ret)
                hHook = 0;
            return ret;
        }

        public event CombinedKeyEventHandler CombinedKeyUp;
        protected void OnCombinedKeyUp(List<Keys> keys)
        {
            if (CombinedKeyUp != null)
            {
                CombinedKeyUp(keys);
            }
        }
        protected void OnCombinedKeyUp(object keys)
        {
            if (keys is List<Keys>)
            {
                OnCombinedKeyUp((List<Keys>)keys);
            }
        }

        public event CombinedKeyEventHandler CombinedKeyDown;
        protected void OnCombinedKeyDown(List<Keys> keys)
        {
            if (CombinedKeyDown != null)
            {
                CombinedKeyDown(keys);
            }
        }
        protected void OnCombinedKeyDown(object keys)
        {
            if (keys is List<Keys>)
            {
                OnCombinedKeyDown((List<Keys>)keys);
            }
        }

        private int KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode < 0)
            {
                return CallNextHookEx(hHook, nCode, wParam, lParam);                
            }
            else
            {
                keyboardHookStruct MyKeyboardHookStruct = (keyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(keyboardHookStruct));
                Keys key = (Keys)MyKeyboardHookStruct.vkCode;

                if (((int)wParam == WM_KEYDOWN) || ((int)wParam == WM_SYSKEYDOWN))
                {
                    lock (KeyCodesPressed)
                    {
                        if (!KeyCodesPressed.Contains(key))
                        {
                            KeyCodesPressed.Add((Keys)MyKeyboardHookStruct.vkCode);
                            new Thread(new ParameterizedThreadStart(OnCombinedKeyDown)) { Name = "OnCombinedKeyDownThread" }.Start(KeyCodesPressed.Select(a => a).ToList());
                        }
                    }
                }
                else if (((int)wParam == WM_KEYUP) || ((int)wParam == WM_SYSKEYUP))
                {
                    lock (KeyCodesPressed)
                    {
                        new Thread(new ParameterizedThreadStart(OnCombinedKeyUp)) { Name = "OnCombinedKeyUpThread" }.Start(KeyCodesPressed.Select(a => a).ToList());
                        KeyCodesPressed.RemoveAll(k => k == (Keys)MyKeyboardHookStruct.vkCode);
                    }
                }

                return CallNextHookEx(hHook, nCode, wParam, lParam); 
            }
        }

    }

}
